Goal

Perform normalization and DE analysis using the limma package. We recommend to use the limma-voom method for DE analysis, as it is the most robust method for this type of data, coupled with the duplicateCorrelation function to account for the batch effect.

DE question

For a given cell type, do we see differences between the normal and DKD segments?

Input Data

We will use the tables generated in the previous QC step, they are found in the results folder.

  • Counts: Dataframe, or table, including a column with the gene name (Default, TargetName).

  • Sample Annotation: Sample annotation, it should include a segment name column, (Default, SegmentDisplayName) and Coordinate columns (Default, ROICoordinateX and ROICoordinateY).

str(metadata)
'data.frame':   201 obs. of  13 variables:
 $ segment_name    : chr  "DSP-1001250007851-H-A02.dcc" "DSP-1001250007851-H-A03.dcc" "DSP-1001250007851-H-A04.dcc" "DSP-1001250007851-H-A05.dcc" ...
 $ slide_name      : chr  "disease3" "disease3" "disease3" "disease3" ...
 $ region          : chr  "glomerulus" "glomerulus" "glomerulus" "glomerulus" ...
 $ segment         : chr  "Geometric Segment" "Geometric Segment" "Geometric Segment" "Geometric Segment" ...
 $ class           : Factor w/ 2 levels "normal","DKD": 2 2 2 2 2 2 2 2 2 2 ...
 $ aoi             : chr  "Geometric Segment-aoi-001" "Geometric Segment-aoi-001" "Geometric Segment-aoi-001" "Geometric Segment-aoi-001" ...
 $ roi             : num  7 8 9 10 11 12 13 14 15 16 ...
 $ area            : num  31798 16920 14312 20033 27583 ...
 $ nuclei          : num  225 132 114 89 132 169 105 55 164 92 ...
 $ pathology       : chr  "abnormal" "abnormal" "abnormal" "abnormal" ...
 $ ROI_Coordinate_X: num  89.1 237.7 174 184.3 374.3 ...
 $ ROI_Coordinate_Y: num  108.4 124.2 167.8 69.4 177.3 ...
 $ CellType        : Factor w/ 3 levels "glomeruli","ProximalTubules",..: 1 1 1 1 1 1 1 1 1 1 ...

Dataset exploration

Normalization

We will start with the raw counts and Compute Q3 normalization.

## DGEList: Create object from counts, metadata and main variable of interest.
dge <- DGEList(counts = counts, samples = metadata)

## Q3 normalization
dge <- calcNormFactors(dge, method = 'upperquartile')

To obtain \(log_2CPM\) values, we will use voom, in this case we use a simple design matrix, that captures our main variables of interest and the batch variable.

## Initial design matrix: Include key variables that explain the variation in the study. 
design = model.matrix(~CellType + class + slide_name, data = metadata)
logcpm <- voom(dge, design)$E

PCA

We will perform a PCA analysis to explore the variation in the dataset, using logCPM values.

biplot(pca, pointSize = 2, colby = "slide_name", lab = NULL, legendPosition = 'right')
biplot(pca, x = 'PC3', y = 'PC4', pointSize = 2, colby = "slide_name", lab = NULL, legendPosition = 'right')


biplot(pca, pointSize = 2, colby = "CellType", shape = "class", lab = NULL, legendPosition = 'right')

biplot(pca, x = 'PC3', y = 'PC4', pointSize = 2, colby = "CellType", shape = "class", lab = NULL, legendPosition = 'right')

DE analysis: limma-voom

DE analysis performed using the limma package. edgeR, limma-voom and DESeq2 are recommended for GeoMx data.

Strong preference for limma-voom using duplicate correlation, as it is the most robust method for this type of data. Does not modify the variation in the dataset and assumes a mixed effect for the batch variable.

DESeq2 is recommended when you have raw counts, and you want to include batch variables as covariates but you also have a full-rank matrix (Slide is not confounded with the variable of interest).

In this case, we will start from the original dataset, since we will use the duplicated correlation to account for the batch effect.

Design Model: ~ CellType + Class:CellType

## Block variable set to the batch variable
block_var = metadata$slide_name

## Design model. 
design = model.matrix(~0 + CellType + class:CellType, data = metadata)
## Update contrasts names. 
colnames(design) <- gsub("CellType|class", "", colnames(design)) %>% gsub(pattern = ':', rep = '_')

Biological Coefficient of Variation

keep <- filterByExpr(dge, design)
dge_all <- dge[keep, ]
dge_all <- estimateDisp(dge_all, design = design, robust = TRUE)

plotBCV(dge_all, ylim = c(0, 1.3))
bcv_df <- data.frame(
  'BCV' = sqrt(dge_all$tagwise.dispersion),
  'AveLogCPM' = dge_all$AveLogCPM,
  'gene_id' = rownames(dge_all)
)

highbcv <- bcv_df$BCV > 0.8
highbcv_df <- bcv_df[highbcv, ]
points(highbcv_df$AveLogCPM, highbcv_df$BCV, col = "red")
text(highbcv_df$AveLogCPM, highbcv_df$BCV, labels = highbcv_df$gene_id, pos = 4)

Fit model

# Estimate correlation within slides
corfit <- duplicateCorrelation(voom(dge, design), block = block_var)

# Run voom with duplicate correlation
v <- voom(dge, design, block = block_var, correlation = corfit$consensus, plot = T)


# Fit the model
fit <- lmFit(v, design, block = block_var, correlation = corfit$consensus)
fit <- eBayes(fit)

We also check the value of the concensus correlation, part of the output of the duplicateCorrelation function.

If the value is < 0.1, blocking might not as needed. If the value is >0.5, you might need a second round of duplicateCorrelation, same code, but using the residuals of the first model.

In this case, corfit$consensus.correlation is 0.205, so the batch seems to have an observable effect.

colnames( fit$coefficients )
[1] "glomeruli"           "ProximalTubules"     "DistalTubules"       "glomeruli_DKD"       "ProximalTubules_DKD"
[6] "DistalTubules_DKD"  

Results

In this case we will focus on DE between disease and normal samples in glomeruli, which is represented by the glomeruli_DKD coefficient.

Top 20 DEGs DKD vs Normal, glomeruli
Gene logFC P.Value adj.P.Val
TSPY1 1.707835 0.0000002 0.0000373
SUPT7L -1.528025 0.0000000 0.0000001
IL1RL1 -1.484195 0.0000090 0.0009137
NELL1 1.473469 0.0000013 0.0002036
DEFA1B 1.450012 0.0006777 0.0230282
CDKN1C -1.416080 0.0000000 0.0000000
SLC2A3 1.376519 0.0000002 0.0000410
NDNF -1.332004 0.0000000 0.0000000
RBMY1J 1.322612 0.0000003 0.0000522
RPS4Y1 1.310669 0.0001131 0.0065576
PCOLCE2 -1.273577 0.0000000 0.0000000
PRY 1.255908 0.0000807 0.0049619
INMT -1.252914 0.0000001 0.0000324
DCN -1.231292 0.0000001 0.0000274
ESM1 -1.194987 0.0000099 0.0009685
MME -1.190490 0.0000000 0.0000006
EPAS1 -1.181506 0.0000000 0.0000051
S100A9 1.179379 0.0017487 0.0446220
HIPK2 -1.171479 0.0000000 0.0000000
S100A8 1.152229 0.0004539 0.0177107

Heatmaps

For visualization, we can use one of the batch corrected datasets, in this case, we will use the limma batch corrected dataset.

We will plot the logNorm expression in the Top 20 DEG in Glomeruli.

## Heatmaps: Top 20 DEGs. 
top_20 = filter(de, adj.P.Val < 0.05 & abs(logFC) >= log2(1.5)) %>% 
    dplyr::select(Gene, logFC, P.Value, adj.P.Val) %>% 
    arrange(desc(abs(logFC))) %>% head(n = 20) %>% pull('Gene')

norm_mx = logcpm[top_20, metadata$CellType == i]
scale(norm_mx) -> norm_mx


ComplexHeatmap::Heatmap(logcpm[top_20, metadata$CellType == i], 
                        name = 'logCPM', col = c('white', 'red3'),
                        column_split = metadata$class[metadata$CellType == i],
                        cluster_rows = F, cluster_columns = F, show_row_names = T, show_column_names = F)


norm_mx = logcpm[top_20, metadata$CellType == i] %>% t %>% scale()

ComplexHeatmap::Heatmap(t(norm_mx), 
                        name = 'z-score', 
                        column_split = metadata$class[metadata$CellType == i],
                        cluster_rows = F, cluster_columns = F, show_row_names = T, show_column_names = F)

R Session

map(sessionInfo()$otherPkgs, ~.x$Version)
LS0tCnRpdGxlOiAnREUgYW5hbHlzaXMgdXNpbmcgbGltbWE6IE5vcm1hbGl6YXRpb24sIEJhdGNoIGNvcnJlY3Rpb24gYW5kIERFIGFuYWx5c2lzJwphdXRob3I6ICJEaWFuYSBWZXJhIENydXoiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQpzdWJ0aXRsZTogS2lkbmV5IGV4YW1wbGUgZGF0YXNldAotLS0KCiMjIEdvYWwKClBlcmZvcm0gbm9ybWFsaXphdGlvbiBhbmQgREUgYW5hbHlzaXMgdXNpbmcgdGhlIGxpbW1hIHBhY2thZ2UuIFdlIHJlY29tbWVuZCB0byB1c2UgdGhlIGBsaW1tYS12b29tYCBtZXRob2QgZm9yIERFIGFuYWx5c2lzLCBhcyBpdCBpcyB0aGUgbW9zdCByb2J1c3QgbWV0aG9kIGZvciB0aGlzIHR5cGUgb2YgZGF0YSwgY291cGxlZCB3aXRoIHRoZSBgZHVwbGljYXRlQ29ycmVsYXRpb25gIGZ1bmN0aW9uIHRvIGFjY291bnQgZm9yIHRoZSBiYXRjaCBlZmZlY3QuIAoKIyMgREUgcXVlc3Rpb24gCgoqKkZvciBhIGdpdmVuIGNlbGwgdHlwZSwgZG8gd2Ugc2VlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIG5vcm1hbCBhbmQgREtEIHNlZ21lbnRzPyoqCgoKIyMgSW5wdXQgRGF0YQoKV2Ugd2lsbCB1c2UgdGhlIHRhYmxlcyBnZW5lcmF0ZWQgaW4gdGhlIHByZXZpb3VzIFFDIHN0ZXAsIHRoZXkgYXJlIGZvdW5kIGluIHRoZSByZXN1bHRzIGZvbGRlci4KCi0gICAqKkNvdW50cyoqOiBEYXRhZnJhbWUsIG9yIHRhYmxlLCBpbmNsdWRpbmcgYSBjb2x1bW4gd2l0aCB0aGUgZ2VuZSBuYW1lIChEZWZhdWx0LCBgVGFyZ2V0TmFtZWApLgoKLSAgICoqU2FtcGxlIEFubm90YXRpb24qKjogU2FtcGxlIGFubm90YXRpb24sIGl0IHNob3VsZCBpbmNsdWRlIGEgc2VnbWVudCBuYW1lIGNvbHVtbiwgKERlZmF1bHQsIGBTZWdtZW50RGlzcGxheU5hbWVgKSBhbmQgQ29vcmRpbmF0ZSBjb2x1bW5zIChEZWZhdWx0LCBgUk9JQ29vcmRpbmF0ZVhgIGFuZCBgUk9JQ29vcmRpbmF0ZVlgKS4KCgoKYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGfQojIyBMaWJyYXJpZXMKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkoZ2dhbGx1dmlhbCkKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KFBDQXRvb2xzKQpgYGAKCmBgYHtyIHN0dWR5X2RhdGEsIG1lc3NhZ2UgPSBGfQpjb3VudHMgPSByZWFkX3RzdignLi4vcmVzdWx0cy90aWR5X2NvdW50cy50c3YnKSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCdUYXJnZXROYW1lJykgJT4lIGFzLm1hdHJpeCgpCgojIyBNZXRhZGF0YTogRm9ybWF0IGFuZCBlbnN1cmUgdGhhdCB0aGUgdmFyaWFibGVzIG9mIGludGVyZXN0IGFyZSBmYWN0b3JzLCBhbmQgaW4gdGhlIGRlc2lyZWQgb3JkZXIuCm1ldGFkYXRhID0gcmVhZF90c3YoJy4uL3Jlc3VsdHMvdGlkeV9tZXRhZGF0YS50c3YnKSAlPiUgYXMuZGF0YS5mcmFtZSgpCnJvd25hbWVzKG1ldGFkYXRhKSA9IG1ldGFkYXRhJHNlZ21lbnRfbmFtZQptZXRhZGF0YSA8LSBtdXRhdGUobWV0YWRhdGEsIAogICAgICAgQ2VsbFR5cGUgPSBjYXNlX3doZW4oc2VnbWVudCA9PSAnR2VvbWV0cmljIFNlZ21lbnQnIH4gJ2dsb21lcnVsaScsIHNlZ21lbnQgPT0gJ1BhbkNLLScgfiAnRGlzdGFsVHVidWxlcycsIFRSVUUgfiAnUHJveGltYWxUdWJ1bGVzJykgJT4lIGZhY3RvcihsZXZlbHMgPSBjKCdnbG9tZXJ1bGknLCAnUHJveGltYWxUdWJ1bGVzJywgJ0Rpc3RhbFR1YnVsZXMnKSksIAogICAgICAgY2xhc3MgPSBmYWN0b3IoY2xhc3MsIGxldmVscyA9IGMoJ25vcm1hbCcsICdES0QnKSkKICAgICAgICkKCnN0cihtZXRhZGF0YSkKYGBgCgojIyBEYXRhc2V0IGV4cGxvcmF0aW9uCgoKYGBge3J9CmRwbHlyOjpjb3VudChtZXRhZGF0YSwgc2xpZGVfbmFtZSwgQ2VsbFR5cGUsIHNlZ21lbnQsIHJlZ2lvbiwgY2xhc3MpCmBgYAoKCiMjIE5vcm1hbGl6YXRpb24KCldlIHdpbGwgc3RhcnQgd2l0aCB0aGUgcmF3IGNvdW50cyBhbmQgQ29tcHV0ZSBRMyBub3JtYWxpemF0aW9uLgoKYGBge3J9CiMjIERHRUxpc3Q6IENyZWF0ZSBvYmplY3QgZnJvbSBjb3VudHMsIG1ldGFkYXRhIGFuZCBtYWluIHZhcmlhYmxlIG9mIGludGVyZXN0LgpkZ2UgPC0gREdFTGlzdChjb3VudHMgPSBjb3VudHMsIHNhbXBsZXMgPSBtZXRhZGF0YSkKCiMjIFEzIG5vcm1hbGl6YXRpb24KZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UsIG1ldGhvZCA9ICd1cHBlcnF1YXJ0aWxlJykKYGBgCgpUbyBvYnRhaW4gJGxvZ18yQ1BNJCB2YWx1ZXMsIHdlIHdpbGwgdXNlIHZvb20sIGluIHRoaXMgY2FzZSB3ZSB1c2UgYSBzaW1wbGUgZGVzaWduIG1hdHJpeCwgdGhhdCBjYXB0dXJlcyBvdXIgbWFpbiB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgYW5kIHRoZSBiYXRjaCB2YXJpYWJsZS4KCmBgYHtyfQojIyBJbml0aWFsIGRlc2lnbiBtYXRyaXg6IEluY2x1ZGUga2V5IHZhcmlhYmxlcyB0aGF0IGV4cGxhaW4gdGhlIHZhcmlhdGlvbiBpbiB0aGUgc3R1ZHkuIApkZXNpZ24gPSBtb2RlbC5tYXRyaXgofkNlbGxUeXBlICsgY2xhc3MgKyBzbGlkZV9uYW1lLCBkYXRhID0gbWV0YWRhdGEpCmxvZ2NwbSA8LSB2b29tKGRnZSwgZGVzaWduKSRFCmBgYAoKCgojIyMgUENBCgpXZSB3aWxsIHBlcmZvcm0gYSBQQ0EgYW5hbHlzaXMgdG8gZXhwbG9yZSB0aGUgdmFyaWF0aW9uIGluIHRoZSBkYXRhc2V0LCB1c2luZyBsb2dDUE0gdmFsdWVzLiAKCmBgYHtyfQpwY2EgPC0gcGNhKGxvZ2NwbSwgbWV0YSA9IG1ldGFkYXRhLCBzY2FsZSA9IFQpCgpzY3JlZXBsb3QocGNhLCBjb21wb25lbnRzID0gMTo1MCwgYXhpc0xhYlNpemUgPSAxMikKYGBgCgoKYGBge3J9CmJpcGxvdChwY2EsIHBvaW50U2l6ZSA9IDIsIGNvbGJ5ID0gInNsaWRlX25hbWUiLCBsYWIgPSBOVUxMLCBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcpCmJpcGxvdChwY2EsIHggPSAnUEMzJywgeSA9ICdQQzQnLCBwb2ludFNpemUgPSAyLCBjb2xieSA9ICJzbGlkZV9uYW1lIiwgbGFiID0gTlVMTCwgbGVnZW5kUG9zaXRpb24gPSAncmlnaHQnKQoKYmlwbG90KHBjYSwgcG9pbnRTaXplID0gMiwgY29sYnkgPSAiQ2VsbFR5cGUiLCBzaGFwZSA9ICJjbGFzcyIsIGxhYiA9IE5VTEwsIGxlZ2VuZFBvc2l0aW9uID0gJ3JpZ2h0JykKYmlwbG90KHBjYSwgeCA9ICdQQzMnLCB5ID0gJ1BDNCcsIHBvaW50U2l6ZSA9IDIsIGNvbGJ5ID0gIkNlbGxUeXBlIiwgc2hhcGUgPSAiY2xhc3MiLCBsYWIgPSBOVUxMLCBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcpCmBgYAoKCiMjIERFIGFuYWx5c2lzOiBgbGltbWEtdm9vbWAKCkRFIGFuYWx5c2lzIHBlcmZvcm1lZCB1c2luZyB0aGUgYGxpbW1hYCBwYWNrYWdlLiBlZGdlUiwgbGltbWEtdm9vbSBhbmQgREVTZXEyIGFyZSByZWNvbW1lbmRlZCBmb3IgR2VvTXggZGF0YS4KClN0cm9uZyBwcmVmZXJlbmNlIGZvciBgbGltbWEtdm9vbWAgdXNpbmcgZHVwbGljYXRlIGNvcnJlbGF0aW9uLCBhcyBpdCBpcyB0aGUgbW9zdCByb2J1c3QgbWV0aG9kIGZvciB0aGlzIHR5cGUgb2YgZGF0YS4gRG9lcyBub3QgbW9kaWZ5IHRoZSB2YXJpYXRpb24gaW4gdGhlIGRhdGFzZXQgYW5kIGFzc3VtZXMgYSBtaXhlZCBlZmZlY3QgZm9yIHRoZSBiYXRjaCB2YXJpYWJsZS4KCkRFU2VxMiBpcyByZWNvbW1lbmRlZCB3aGVuIHlvdSBoYXZlIHJhdyBjb3VudHMsIGFuZCB5b3Ugd2FudCB0byBpbmNsdWRlIGJhdGNoIHZhcmlhYmxlcyBhcyBjb3ZhcmlhdGVzIGJ1dCB5b3UgYWxzbyBoYXZlIGEgZnVsbC1yYW5rIG1hdHJpeCAoU2xpZGUgaXMgbm90IGNvbmZvdW5kZWQgd2l0aCB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QpLgoKSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIHN0YXJ0IGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQsIHNpbmNlIHdlIHdpbGwgdXNlIHRoZSBkdXBsaWNhdGVkIGNvcnJlbGF0aW9uIHRvIGFjY291bnQgZm9yIHRoZSBiYXRjaCBlZmZlY3QuCgoqKkRlc2lnbiBNb2RlbCoqOiBcfiBDZWxsVHlwZSArIENsYXNzOkNlbGxUeXBlCgoKYGBge3J9CiMjIEJsb2NrIHZhcmlhYmxlIHNldCB0byB0aGUgYmF0Y2ggdmFyaWFibGUKYmxvY2tfdmFyID0gbWV0YWRhdGEkc2xpZGVfbmFtZQoKIyMgRGVzaWduIG1vZGVsLiAKZGVzaWduID0gbW9kZWwubWF0cml4KH4wICsgQ2VsbFR5cGUgKyBjbGFzczpDZWxsVHlwZSwgZGF0YSA9IG1ldGFkYXRhKQojIyBVcGRhdGUgY29udHJhc3RzIG5hbWVzLiAKY29sbmFtZXMoZGVzaWduKSA8LSBnc3ViKCJDZWxsVHlwZXxjbGFzcyIsICIiLCBjb2xuYW1lcyhkZXNpZ24pKSAlPiUgZ3N1YihwYXR0ZXJuID0gJzonLCByZXAgPSAnXycpCmBgYAoKIyMjIyBCaW9sb2dpY2FsIENvZWZmaWNpZW50IG9mIFZhcmlhdGlvbgoKYGBge3IsIHdhcm5pbmcgPSBGfQprZWVwIDwtIGZpbHRlckJ5RXhwcihkZ2UsIGRlc2lnbikKZGdlX2FsbCA8LSBkZ2Vba2VlcCwgXQpkZ2VfYWxsIDwtIGVzdGltYXRlRGlzcChkZ2VfYWxsLCBkZXNpZ24gPSBkZXNpZ24sIHJvYnVzdCA9IFRSVUUpCgpwbG90QkNWKGRnZV9hbGwsIHlsaW0gPSBjKDAsIDEuMykpCmJjdl9kZiA8LSBkYXRhLmZyYW1lKAogICdCQ1YnID0gc3FydChkZ2VfYWxsJHRhZ3dpc2UuZGlzcGVyc2lvbiksCiAgJ0F2ZUxvZ0NQTScgPSBkZ2VfYWxsJEF2ZUxvZ0NQTSwKICAnZ2VuZV9pZCcgPSByb3duYW1lcyhkZ2VfYWxsKQopCgpoaWdoYmN2IDwtIGJjdl9kZiRCQ1YgPiAwLjgKaGlnaGJjdl9kZiA8LSBiY3ZfZGZbaGlnaGJjdiwgXQpwb2ludHMoaGlnaGJjdl9kZiRBdmVMb2dDUE0sIGhpZ2hiY3ZfZGYkQkNWLCBjb2wgPSAicmVkIikKdGV4dChoaWdoYmN2X2RmJEF2ZUxvZ0NQTSwgaGlnaGJjdl9kZiRCQ1YsIGxhYmVscyA9IGhpZ2hiY3ZfZGYkZ2VuZV9pZCwgcG9zID0gNCkKYGBgCgojIyMjIEZpdCBtb2RlbAoKYGBge3J9CiMgRXN0aW1hdGUgY29ycmVsYXRpb24gd2l0aGluIHNsaWRlcwpjb3JmaXQgPC0gZHVwbGljYXRlQ29ycmVsYXRpb24odm9vbShkZ2UsIGRlc2lnbiksIGJsb2NrID0gYmxvY2tfdmFyKQoKIyBSdW4gdm9vbSB3aXRoIGR1cGxpY2F0ZSBjb3JyZWxhdGlvbgp2IDwtIHZvb20oZGdlLCBkZXNpZ24sIGJsb2NrID0gYmxvY2tfdmFyLCBjb3JyZWxhdGlvbiA9IGNvcmZpdCRjb25zZW5zdXMsIHBsb3QgPSBUKQoKIyBGaXQgdGhlIG1vZGVsCmZpdCA8LSBsbUZpdCh2LCBkZXNpZ24sIGJsb2NrID0gYmxvY2tfdmFyLCBjb3JyZWxhdGlvbiA9IGNvcmZpdCRjb25zZW5zdXMpCmZpdCA8LSBlQmF5ZXMoZml0KQpgYGAKCldlIGFsc28gY2hlY2sgdGhlIHZhbHVlIG9mIHRoZSBjb25jZW5zdXMgY29ycmVsYXRpb24sIHBhcnQgb2YgdGhlIG91dHB1dCBvZiB0aGUgYGR1cGxpY2F0ZUNvcnJlbGF0aW9uYCBmdW5jdGlvbi4KCklmIHRoZSB2YWx1ZSBpcyA8IDAuMSwgYmxvY2tpbmcgbWlnaHQgbm90IGFzIG5lZWRlZC4gCklmIHRoZSB2YWx1ZSBpcyA+MC41LCB5b3UgbWlnaHQgbmVlZCBhIHNlY29uZCByb3VuZCBvZiBkdXBsaWNhdGVDb3JyZWxhdGlvbiwgc2FtZSBjb2RlLCBidXQgdXNpbmcgdGhlIHJlc2lkdWFscyBvZiB0aGUgZmlyc3QgbW9kZWwuIAoKSW4gdGhpcyBjYXNlLCBgY29yZml0JGNvbnNlbnN1cy5jb3JyZWxhdGlvbmAgaXMgKipgciByb3VuZChjb3JmaXQkY29uc2Vuc3VzLmNvcnJlbGF0aW9uLCBkaWdpdHMgPSAzKWAqKiwgc28gdGhlIGJhdGNoIHNlZW1zIHRvIGhhdmUgYW4gb2JzZXJ2YWJsZSBlZmZlY3QuIAoKYGBge3J9CiMgRXh0cmFjdCByZXN1bHRzIGZvciBhIHNwZWNpZmljIGNsYXNzIGVmZmVjdCB3aXRoaW4gYSBjZWxsIHR5cGUuCmNvbG5hbWVzKCBmaXQkY29lZmZpY2llbnRzICkKYGBgCgojIyMjIFJlc3VsdHMKCkluIHRoaXMgY2FzZSB3ZSB3aWxsIGZvY3VzIG9uIERFIGJldHdlZW4gZGlzZWFzZSBhbmQgbm9ybWFsIHNhbXBsZXMgaW4gZ2xvbWVydWxpLCB3aGljaCBpcyByZXByZXNlbnRlZCBieSB0aGUgYGdsb21lcnVsaV9ES0RgIGNvZWZmaWNpZW50LgoKYGBge3IsIHdhcm5pbmdzID0gRn0KdGhpc19jb2VmID0gJ2dsb21lcnVsaV9ES0QnCmkgPSAnZ2xvbWVydWxpJwoKIyMgRXh0cmFjdCBhbGwgdGhlIHJlc3VsdHMgcGVyIGdlbmU6IE5vIGZpbHRlciBmb3IgcC12YWx1ZSBub3IgbG9nRkMsIHdpdGggcmVndWxhciBhZGp1c3RtZW50LgpkZSA8LSB0b3BUYWJsZShmaXQsIGNvZWY9dGhpc19jb2VmLCAgbnVtYmVyPUluZikgJT4lIG11dGF0ZShDZWxsVHlwZSA9IGkpICU+JSByb3duYW1lc190b19jb2x1bW4oJ0dlbmUnKQogIAojIyBTaG93IERFLiAKZmlsdGVyKGRlLCBhZGouUC5WYWwgPCAwLjA1ICYgYWJzKGxvZ0ZDKSA+PSBsb2cyKDEuNSkpICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoR2VuZSwgbG9nRkMsIFAuVmFsdWUsIGFkai5QLlZhbCkgJT4lIAogICAgYXJyYW5nZShkZXNjKGFicyhsb2dGQykpKSAlPiUgaGVhZChuID0gMjApICU+JSAKICAgIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUwKCdUb3AgMjAgREVHc1xuREtEIHZzIE5vcm1hbCwgJywgaSkpCmBgYAoKCmBgYHtyLCB3YXJuaW5ncyA9IEZ9CiMjIFZvbGNhbm8gcGxvdApkZSAlPiUgbXV0YXRlKHNpZyA9IGFkai5QLlZhbCA8IDAuMDUgJiBhYnMobG9nRkMpID49IGxvZzIoMS41KSwgZ2VuZSA9IGlmZWxzZShzaWcgPT0gVCwgR2VuZSwgJycpKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChhZGouUC5WYWwpLCBjb2xvciA9IHNpZywgbGFiZWwgPSBnZW5lKSkgKyB0aGVtZV9idygpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMywgY29sb3IgPSAnb3JhbmdlJywgbGluZXR5cGUgPSAnZGFzaGVkJykgKyAKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTAuNTksIDAuNTkpLCAgY29sb3IgPSAnZ3JleTUwJywgbGluZXR5cGUgPSAnZGFzaGVkJykgKwogICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArIGdlb21fdGV4dF9yZXBlbChzaXplID0gMywgY29sb3IgPSAnYmxhY2snLCBtYXgub3ZlcmxhcHMgPSAxNSkgKyAKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGBGQUxTRWAgPSAnYmxhY2snLCBgVFJVRWAgPSAncmVkMycpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsgbGFicyh0aXRsZSA9IHBhc3RlKCdES0QgdnMgTm9ybWFsJywgaSksIHkgPSAnLWxvZzEwKEFkai4gUC12YWx1ZSknLCB4ID0gJ2xvZzJGQycpCmBgYAoKIyMjIyBIZWF0bWFwcwoKRm9yIHZpc3VhbGl6YXRpb24sIHdlIGNhbiB1c2Ugb25lIG9mIHRoZSBiYXRjaCBjb3JyZWN0ZWQgZGF0YXNldHMsIGluIHRoaXMgY2FzZSwgd2Ugd2lsbCB1c2UgdGhlIGxpbW1hIGJhdGNoIGNvcnJlY3RlZCBkYXRhc2V0LgoKV2Ugd2lsbCBwbG90IHRoZSBsb2dOb3JtIGV4cHJlc3Npb24gaW4gdGhlIFRvcCAyMCBERUcgaW4gR2xvbWVydWxpLgoKYGBge3J9CiMjIEhlYXRtYXBzOiBUb3AgMjAgREVHcy4gCnRvcF8yMCA9IGZpbHRlcihkZSwgYWRqLlAuVmFsIDwgMC4wNSAmIGFicyhsb2dGQykgPj0gbG9nMigxLjUpKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KEdlbmUsIGxvZ0ZDLCBQLlZhbHVlLCBhZGouUC5WYWwpICU+JSAKICAgIGFycmFuZ2UoZGVzYyhhYnMobG9nRkMpKSkgJT4lIGhlYWQobiA9IDIwKSAlPiUgcHVsbCgnR2VuZScpCgpub3JtX214ID0gbG9nY3BtW3RvcF8yMCwgbWV0YWRhdGEkQ2VsbFR5cGUgPT0gaV0Kc2NhbGUobm9ybV9teCkgLT4gbm9ybV9teAoKCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKGxvZ2NwbVt0b3BfMjAsIG1ldGFkYXRhJENlbGxUeXBlID09IGldLCAKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICdsb2dDUE0nLCBjb2wgPSBjKCd3aGl0ZScsICdyZWQzJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9zcGxpdCA9IG1ldGFkYXRhJGNsYXNzW21ldGFkYXRhJENlbGxUeXBlID09IGldLAogICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBGLCBjbHVzdGVyX2NvbHVtbnMgPSBGLCBzaG93X3Jvd19uYW1lcyA9IFQsIHNob3dfY29sdW1uX25hbWVzID0gRikKCm5vcm1fbXggPSBsb2djcG1bdG9wXzIwLCBtZXRhZGF0YSRDZWxsVHlwZSA9PSBpXSAlPiUgdCAlPiUgc2NhbGUoKQoKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAodChub3JtX214KSwgCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAnei1zY29yZScsIAogICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fc3BsaXQgPSBtZXRhZGF0YSRjbGFzc1ttZXRhZGF0YSRDZWxsVHlwZSA9PSBpXSwKICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRiwgY2x1c3Rlcl9jb2x1bW5zID0gRiwgc2hvd19yb3dfbmFtZXMgPSBULCBzaG93X2NvbHVtbl9uYW1lcyA9IEYpCgpgYGAKCiMjIFIgU2Vzc2lvbgoKYGBge3J9Cm1hcChzZXNzaW9uSW5mbygpJG90aGVyUGtncywgfi54JFZlcnNpb24pCmBgYAo=